package com.yixing.tech.wcs.service.impl;

import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.enums.YesOrNo;
import com.yixing.tech.common.vo.s3.UserVO;
import com.mongodb.client.result.UpdateResult;
import com.yixing.tech.wcs.constant.DictId;
import com.yixing.tech.wcs.constant.DictName;
import com.yixing.tech.wcs.constant.EntityTemplateId;
import com.yixing.tech.wcs.domain.dto.AreaMappingDTO;
import com.yixing.tech.wcs.domain.po.*;
import com.yixing.tech.wcs.service.ICRUDService;
import com.yixing.tech.wcs.util.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class CRUDServiceImpl implements ICRUDService {

    @Value("${tenant-id: 6204cb763da841718ddd1cf8}")
    private String tenantId;

    @Resource
    MongoTemplate mongoTemplate;

    @Resource
    UserUtil userUtil;

    private String bMatPickingEntranceAreaCode;
    private String bMatPickingExitAreaCode;

    @Override
    public boolean exists(Map<String, Object> kv, String templateId) throws BusinessException {
        Criteria criteria = Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal());
        for(Map.Entry<String, Object> entry : kv.entrySet()) {
            criteria.and(entry.getKey()).is(entry.getValue());
        }
        Query query = new Query(criteria);
        return mongoTemplate.exists(query, templateId);
    }

    @Override
    public <T> T findOne(Map<String, Object> kv, Class<T> entityClass, String templateId) throws BusinessException {
        Criteria criteria = Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal());
        for(Map.Entry<String, Object> entry : kv.entrySet()) {
            criteria.and(entry.getKey()).is(entry.getValue());
        }
        Query query = new Query(criteria);

        return mongoTemplate.findOne(query, entityClass, templateId);
    }

    @Override
    public <T> List<T> find(Map<String, Object> kv, String templateId) throws BusinessException {
        return null;
    }

    @Override
    public long update(Map<String, Object> q, Map<String, Object> u, String templateId) throws BusinessException {
        Criteria criteria = Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal());
        for(Map.Entry<String, Object> map: q.entrySet()) {
            criteria.and(map.getKey()).is(map.getValue());
        }
        Query query = new Query(criteria);

        Update update = new Update();
        update.set("updateTime", new Date());

        for(Map.Entry<String, Object> map: u.entrySet()) {
            update.set(map.getKey(), map.getValue());
        }

        return mongoTemplate.updateMulti(query, update, templateId).getModifiedCount();
    }

    @Override
    public AreaMappingDTO queryAreaMapping(String ruleName, String startArea, String endArea) throws BusinessException {
        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("ruleName").is(ruleName).and("mappingPath").nin(null, new ArrayList<>())));

        operations.add(Aggregation.addFields()
                .addFieldWithValue("startAreaObj", ArrayOperators.ArrayElemAt.arrayOf("mappingPath").elementAt(0))
                .addFieldWithValue("endAreaObj",
                        ArrayOperators.ArrayElemAt.arrayOf("mappingPath").elementAt(-1))
                .build());

        operations.add(Aggregation.unwind("mappingPath", true));

        operations.add(Aggregation.sort(Sort.Direction.DESC, "updateTime"));

        operations.add(Aggregation.group("_id")
                .first("ruleName").as("ruleName")
                .first("startAreaObj.area.code").as("startArea")
                .first("endAreaObj.area.code").as("endArea")
                .push("mappingPath.area.code").as("mappingPath"));

        operations.add(Aggregation.match(Criteria.where("startArea").is(startArea).and("endArea").is(endArea)));
        operations.add(Aggregation.limit(1));

        AggregationResults<AreaMappingDTO> results =  mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.AREA_MAPPING_TEMPLATE, AreaMappingDTO.class);
        return ObjectUtils.isNotEmpty(results.getMappedResults()) ? results.getMappedResults().get(0) : null;
    }

    @Override
    public Area queryAreaByCode(String code) throws BusinessException {
        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("code").is(code)));
        operations.add(Aggregation.addFields()
                .addFieldWithValue("id", ConvertOperators.ToString.toString("$_id"))
                .addFieldWithValue("storageType", ConvertOperators.ToString.toString("$storageType.id"))
                .addFieldWithValue("autoType", ConvertOperators.ToString.toString("$autoType.id"))
                .build());

        AggregationResults<Area> results =  mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.AREA_TEMPLATE, Area.class);
        return ObjectUtils.isNotEmpty(results.getMappedResults()) ? results.getMappedResults().get(0) : null;
    }

    @Override
    public Task queryTaskByTaskCode(String taskCode) {
        List<AggregationOperation> operations = new ArrayList<>();

        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("taskCode").is(taskCode)));

        operations.add(Aggregation.addFields()

                .addFieldWithValue("priority", ConditionalOperators.IfNull.ifNull("priority").then(3))
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .addFieldWithValue("status", ConvertOperators.ToString.toString("$status.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .addFieldWithValue("id", ConvertOperators.ToString.toString("$_id"))
                .build());

        List<Task> taskList = mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.TASK_TEMPLATE, Task.class).getMappedResults();
        return ObjectUtils.isNotEmpty(taskList) ? taskList.get(0) : null;
    }

    @Override
    public SubTask querySubTaskByTaskCode(String taskCode) throws BusinessException {
        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("taskCode").is(taskCode)));

        operations.add(Aggregation.addFields()
                .addFieldWithValue("robotType", ConvertOperators.ToString.toString("$robotType.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .build());
        List<SubTask> subTaskList = mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.SUB_TASK_TEMPLATE, SubTask.class).getMappedResults();

        return ObjectUtils.isNotEmpty(subTaskList) ? subTaskList.get(0) : null;
    }

    @Override
    public List<SubTask> querySubTaskListByParentTask(String parentTask) throws BusinessException {
        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("parentTask").is(parentTask)));

        operations.add(Aggregation.addFields()
                .addFieldWithValue("robotType", ConvertOperators.ToString.toString("$robotType.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .build());

        return mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.SUB_TASK_TEMPLATE, SubTask.class).getMappedResults();
    }

    @Override
    public TaskCallbackInterface queryTaskCallbackInterfaceByBusiness(String business) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("business").is(business));

        return mongoTemplate.findOne(query, TaskCallbackInterface.class, EntityTemplateId.TASK_CALLBACK_INTERFACE);
    }

    @Override
    public TaskCallbackInterface queryTaskExceptionCallbackInterfaceByBusiness(String business) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("business").is(business));

        return mongoTemplate.findOne(query, TaskCallbackInterface.class, EntityTemplateId.TASK_ERROR_CALLBACK_INTERFACE);
    }

    @Override
    public SubTask findSubTaskOne(Map<String, Object> andQ, List<Map<String, Object>> orQ) throws BusinessException {
        List<SubTask> list = findSubTaskByQueryMap(andQ, orQ);
        return ObjectUtils.isNotEmpty(list) ? list.get(0) : null;
    }

    @Override
    public List<SubTask> findSubTask(Map<String, Object> andQ, List<Map<String, Object>> orQ) throws BusinessException {
        return findSubTaskByQueryMap(andQ, orQ);
    }

    private List<SubTask> findSubTaskByQueryMap(Map<String, Object> andQ, List<Map<String, Object>> orQ) {
        Criteria criteria = Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal());
        for(Map.Entry<String, Object> item: andQ.entrySet()) {
            criteria.and(item.getKey()).is(item.getValue());
        }
        criteria.orOperator(orQ.stream().map(item -> {
            Criteria c = Criteria.where("tenantId").is(tenantId);
            for(Map.Entry<String, Object> entry: item.entrySet()) {
                c.and(entry.getKey()).is(entry.getValue());
            }
            return c;
        }).collect(Collectors.toList()));

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(criteria));

        operations.add(Aggregation.addFields()
                .addFieldWithValue("robotType", ConvertOperators.ToString.toString("$robotType.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .build());
        return mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.SUB_TASK_TEMPLATE, SubTask.class).getMappedResults();
    }

    /** 子任务的任务编号唯一 */
    @Override
    public void saveSubTask(SubTask subTask) throws BusinessException {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("tenantId", tenantId);
        dataMap.put("isDelete", YesOrNo.NO.ordinal());
        dataMap.put("createTime", new Date());

        dataMap.put("taskCode", subTask.getTaskCode());
        dataMap.put("parentTask", subTask.getParentTask());
        dataMap.put("forwardTask", subTask.getForwardTask());
        dataMap.put("startArea", subTask.getStartArea());
        dataMap.put("startLoc", subTask.getStartLoc());
        dataMap.put("endArea", subTask.getEndArea());
        dataMap.put("endLoc", subTask.getEndLoc());
        dataMap.put("podCode", subTask.getPodCode());
        dataMap.put("performStatus", Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.UNISSUED)));
        dataMap.put("robotType", ObjectUtils.isNotEmpty(subTask.getRobotType()) ? Document.parse(String.format("{id: ObjectId(\"%s\")}", subTask.getRobotType())) : "");
//        dataMap.put("taskType", subTask.getTaskType());
        dataMap.put("taskType",  ObjectUtils.isNotEmpty(subTask.getTaskType()) ? Document.parse(String.format("{id: ObjectId(\"%s\")}", subTask.getTaskType())) : "");
        dataMap.put("areaCode", subTask.getAreaCode());
        dataMap.put("stationCode", subTask.getStationCode());

        mongoTemplate.save(dataMap, EntityTemplateId.SUB_TASK_TEMPLATE);
    }

    public void saveSubTask(SubTask subTask, String performStatus) throws BusinessException {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("tenantId", tenantId);
        dataMap.put("isDelete", YesOrNo.NO.ordinal());
        dataMap.put("createTime", new Date());

        dataMap.put("taskCode", subTask.getTaskCode());
        dataMap.put("parentTask", subTask.getParentTask());
        dataMap.put("forwardTask", subTask.getForwardTask());
        dataMap.put("startArea", subTask.getStartArea());
        dataMap.put("startLoc", subTask.getStartLoc());
        dataMap.put("endArea", subTask.getEndArea());
        dataMap.put("endLoc", subTask.getEndLoc());
        dataMap.put("podCode", subTask.getPodCode());
        dataMap.put("performStatus", Document.parse(String.format("{id: ObjectId(\"%s\")}", performStatus)));
        dataMap.put("robotType", ObjectUtils.isNotEmpty(subTask.getRobotType()) ? Document.parse(String.format("{id: ObjectId(\"%s\")}", subTask.getRobotType())) : "");
//        dataMap.put("taskType", subTask.getTaskType());
        dataMap.put("taskType",  ObjectUtils.isNotEmpty(subTask.getTaskType()) ? Document.parse(String.format("{id: ObjectId(\"%s\")}", subTask.getTaskType())) : "");
        dataMap.put("areaCode", subTask.getAreaCode());
        dataMap.put("stationCode", subTask.getStationCode());

        mongoTemplate.save(dataMap, EntityTemplateId.SUB_TASK_TEMPLATE);
    }

    @Override
    public void saveSubTask(List<SubTask> subTaskList) throws BusinessException {
        for(SubTask subTask: subTaskList) {
            saveSubTask(subTask);
        }
    }

    @Override
    public long updateSubTaskForwardTaskCode(String taskCode, String forwardTask) throws BusinessException {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("taskCode").is(taskCode));

        Update update = new Update();
        update.set("forwardTask", forwardTask);
        return mongoTemplate.updateFirst(query, update, EntityTemplateId.SUB_TASK_TEMPLATE).getModifiedCount();
    }

    @Override
    public List<Task> listTaskInfo(Map<String, Object> params) throws BusinessException {
        Criteria criteria = Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal());
        if(ObjectUtils.isNotEmpty(params)) {
            for(Map.Entry<String, Object> entry : params.entrySet())
                criteria.and(entry.getKey()).is(entry.getValue());
        }

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(criteria));
        operations.add(Aggregation.addFields()
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .addFieldWithValue("status", ConvertOperators.ToString.toString("$status.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .build());
        operations.add(Aggregation.sort(Sort.Direction.ASC, "createTime"));

        AggregationResults<Task> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.TASK_TEMPLATE, Task.class);
        return results.getMappedResults();
    }

    @Override
    public boolean existsSubTask(String taskCode) throws BusinessException {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("parentTask").is(taskCode));

        return mongoTemplate.exists(query, EntityTemplateId.SUB_TASK_TEMPLATE);
    }

    /** 查询所有料箱交互状态 */
    @Override
    public List<PodInteraction> listPodInteraction() throws BusinessException {
        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())));
        operations.add(Aggregation.addFields()
                .addFieldWithValue("interactionState", ConvertOperators.ToString.toString("$interactionState.id"))
                .build());

        return mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.POD_INTERACTION, PodInteraction.class).getMappedResults();
    }

    /** 新增料箱交互状态 */
    @Override
    public void addPodInteraction(PodInteraction podInteraction) {
        Map<String, Object> data = new HashMap<>();
        data.put("podCode", podInteraction.getPodCode());
        data.put("interactionState",
                Document.parse(String.format("{id: ObjectId(\"%s\")}", ObjectUtils.isNotEmpty(podInteraction.getInteractionState()) ? podInteraction.getInteractionState() : DictId.BoxInteractState.PREPARED)));

        data.put("tenantId", tenantId);
        data.put("isDelete", YesOrNo.NO.ordinal());
        data.put("createTime", new Date());
        data.put("updateTime", new Date());

        mongoTemplate.save(data, EntityTemplateId.POD_INTERACTION);
    }

    /** 更新料箱交互状态 */
    @Override
    public long updatePodInteractionState(List<String> podCodeList, String stateId) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("podCode").in(podCodeList));

        Update update = new Update();
        update.set("interactionState", Document.parse(String.format("{id: ObjectId(\"%s\")}", stateId)));
        update.set("updateTime", new Date());
        return mongoTemplate.updateFirst(query, update, EntityTemplateId.POD_INTERACTION).getModifiedCount();
    }

    /** 删除料箱交互状态 */
    @Override
    public long deletePodInteraction(List<String> podCodeList) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("podCode").in(podCodeList));

        return mongoTemplate.remove(query, EntityTemplateId.POD_INTERACTION).getDeletedCount();
    }

    @Override
    public String queryDictValueByName(String name) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("name").is(name));

        Dict dict = mongoTemplate.findOne(query, Dict.class, EntityTemplateId.DICT);
        return ObjectUtils.isNotEmpty(dict) ? dict.getValue() : "";
    }

    /** 分配空库位，成功后会锁定库位 */
    @Override
    public Location allocateEmptyLoc(List<String> areaCodeList) throws BusinessException {
        if(ObjectUtils.isNotEmpty(areaCodeList)) {
            areaCodeList = areaCodeList.stream().filter(ObjectUtils::isNotEmpty).distinct().collect(Collectors.toList());
        }

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("lock").ne(true).orOperator(Criteria.where("containerCode").exists(false),
                        Criteria.where("containerCode").in(null, ""))));

        if(ObjectUtils.isNotEmpty(areaCodeList)) {
            operations.add(Aggregation.lookup(EntityTemplateId.AREA_TEMPLATE, "area", "code", "areaObj"));
            operations.add(Aggregation.unwind("areaObj", true));
            operations.add(Aggregation.match(Criteria.where("areaObj.tenantId").is(tenantId).and("areaObj.isDelete").ne(YesOrNo.YES.ordinal())
                    .and("areaObj.code").in(areaCodeList)));
        }

        operations.add(Aggregation.addFields().addFieldWithValue("deep", ConditionalOperators.IfNull.ifNull("$deep").then(0)).build());
        operations.add(Aggregation.sort(Sort.Direction.DESC, "deep"));      // 按照深度值从大到小进行排序

        operations.add(Aggregation.lookup(EntityTemplateId.INVENTORY_TEMPLATE, "code", "locationCode", "inventoryObj"));
        operations.add(Aggregation.unwind("inventoryObj", true));
        operations.add(Aggregation.addFields()
                .addFieldWithValue("inventoryObj.realNum", ConditionalOperators.IfNull.ifNull("$inventoryObj.realNum").then(0.0))
                .build());
        operations.add(Aggregation.match(Criteria.where("inventoryObj.realNum").lte(0.0)));

        AggregationResults<Location> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.LOC_TEMPLATE, Location.class);

        /** 如果推荐库位所在的库区为B类物料分拣入口或出口库区，直接返回库区里的第一个库位 */
        if(areaCodeList.contains(DictName.AreaCode.B_MAT_PICKING_ENTRANCE_AREA_CODE) || areaCodeList.contains(DictName.AreaCode.B_MAT_PICKING_EXIT_AREA_CODE)) {
            return ObjectUtils.isNotEmpty(results.getMappedResults()) ? results.getMappedResults().get(0): null;
        }

        for(Location location: results.getMappedResults()) {
            /** 尝试锁定库位 */
            long mc = lockLoc(location.getCode());
            if(mc != 0)
                return location;
        }
        return null;
    }

    @Override
    public long lockLoc(String locCode) throws BusinessException {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("code").is(locCode).and("lock").ne(true));
        Update update = new Update();
        update.set("lock", true);

        if(ObjectUtils.isNotEmpty( RequestContextHolder.getRequestAttributes())) {
            HttpServletRequest request =  ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            UserVO userVO = userUtil.getLoginUser(request.getHeader("x-user"));

            if(ObjectUtils.isNotEmpty(userVO)) {
                update.set("updateId", userVO.getId());    // TODO 获取当前用户信息
                update.set("updateName", userVO.getName());
            }
        }

        update.set("updateTime", new Date());

        UpdateResult result = mongoTemplate.updateMulti(query, update, EntityTemplateId.LOC_TEMPLATE);
        return result.getModifiedCount();
    }

    @Override
    public long unlockLoc(String locCode) throws BusinessException {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("code").is(locCode).and("lock").is(true));
        Update update = new Update();
        update.set("lock", false);

        if(ObjectUtils.isNotEmpty( RequestContextHolder.getRequestAttributes())) {
            HttpServletRequest request =  ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            UserVO userVO = userUtil.getLoginUser(request.getHeader("x-user"));

//            update.set("updateId", userVO.getId());    // TODO 获取当前用户信息
//            update.set("updateName", userVO.getName());
        }

        update.set("updateTime", new Date());

        UpdateResult result = mongoTemplate.updateMulti(query, update, EntityTemplateId.LOC_TEMPLATE);
        return result.getModifiedCount();
    }

    @Override
    public long updateLocationByLocCode(String locCode, Map<String, Object> kv) throws BusinessException {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("code").is(locCode));

        Update update = new Update();
        for(Map.Entry<String, Object> entry : kv.entrySet()) {
            update.set(entry.getKey(), entry.getValue());
        }

        if(ObjectUtils.isNotEmpty( RequestContextHolder.getRequestAttributes())) {
            HttpServletRequest request =  ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            UserVO userVO = userUtil.getLoginUser(request.getHeader("x-user"));

            if(ObjectUtils.isNotEmpty(userVO)) {
                update.set("updateId", userVO.getId());   // TODO 获取用户信息
                update.set("updateName", userVO.getName());
            }
        }
        update.set("updateTime", new Date());

        return mongoTemplate.updateFirst(query, update, EntityTemplateId.LOC_TEMPLATE).getModifiedCount();
    }

    @Override
    public SubTask findExecutingSubTaskOutBoundOneByPodCode(String podCode) throws BusinessException {
        Criteria criteria = Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("podCode").is(podCode)
                .and("performStatus").is(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.RUNNING)))
                .and("taskType").is(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskType.STOCK_OUT)));

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(criteria));

        operations.add(Aggregation.addFields()
                .addFieldWithValue("robotType", ConvertOperators.ToString.toString("$robotType.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .build());

        operations.add(Aggregation.sort(Sort.Direction.DESC, "createTime"));

        AggregationResults<SubTask> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.SUB_TASK_TEMPLATE, SubTask.class);
        return ObjectUtils.isNotEmpty(results.getMappedResults()) ? results.getMappedResults().get(0) : null;
    }

    @Override
    public SubTask findExecutingSubTaskInBoundOneByPodCode(String podCode) throws BusinessException {
        Criteria criteria = Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("podCode").is(podCode)
                .and("performStatus").is(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.RUNNING)))
                .and("taskType").is(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskType.STOCK_IN)));

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(criteria));

        operations.add(Aggregation.addFields()
                .addFieldWithValue("robotType", ConvertOperators.ToString.toString("$robotType.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .build());

        operations.add(Aggregation.sort(Sort.Direction.DESC, "createTime"));

        AggregationResults<SubTask> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.SUB_TASK_TEMPLATE, SubTask.class);
        return ObjectUtils.isNotEmpty(results.getMappedResults()) ? results.getMappedResults().get(0) : null;
    }

    @Override
    public Map findPodByPodCode(String podCode) throws BusinessException {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("code").is(podCode));

        return mongoTemplate.findOne(query, Map.class, EntityTemplateId.POD_TEMPLATE);
    }

    @Override
    @Async
    public long updateCameraPodCode(String podCode) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal()));
        Update update = new Update();
        update.set("containerCode", podCode);
        update.set("updateTime", new Date());

        return mongoTemplate.updateFirst(query, update, EntityTemplateId.CAMERA_DATA_TEMPLATE).getModifiedCount();
    }

    @Override
    public List<Task> queryTaskUnIssuedOrIssued() {
        List<AggregationOperation> operations = new ArrayList<>();

        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
//                .and("").is(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.UNISSUED)))
                .and("performStatus").in(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.UNISSUED)),
                        Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.ISSUED)))));

        operations.add(Aggregation.addFields()

                .addFieldWithValue("priority", ConditionalOperators.IfNull.ifNull("priority").then(3))
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .addFieldWithValue("status", ConvertOperators.ToString.toString("$status.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .addFieldWithValue("id", ConvertOperators.ToString.toString("$_id"))
                .build());

        return mongoTemplate.aggregate(Aggregation.newAggregation(operations), EntityTemplateId.TASK_TEMPLATE, Task.class).getMappedResults();
    }

    @Override
    public boolean existSubTaskUnfinishedByLocCode(String locCode) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("performStatus").in(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.ISSUED)),
                        Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.RUNNING)))
                .orOperator(Criteria.where("startLoc").is(locCode), Criteria.where("endLoc").is(locCode)));
        return mongoTemplate.exists(query, EntityTemplateId.SUB_TASK_TEMPLATE);
    }

    @Override
    public boolean existSubTaskRunningByLocCode(String locCode) {
        Query query = new Query(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("performStatus").is(Document.parse(String.format("{id: ObjectId(\"%s\")}", DictId.TaskPerformStatus.RUNNING)))
                .orOperator(Criteria.where("startLoc").is(locCode), Criteria.where("endLoc").is(locCode)));
        return mongoTemplate.exists(query, EntityTemplateId.SUB_TASK_TEMPLATE);
    }

}
